#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include "tyco_coroutine.h"
#include "tyco_epoll.h"


extern void tyco_coroutine_free(tyco_coroutine *co);


pthread_key_t GLOBAL_SCHED_KEY;



/*
	@brief 是否运行结束
*/
static inline int tyco_schedule_isdone(tyco_schedule *sched) {
	return queue_is_empty(sched->ready) && rbtree_is_empty(sched->wait) &&
		rbtree_is_empty(sched->sleep) ;
}



inline tyco_schedule* tyco_get_sched() {
	return pthread_getspecific(GLOBAL_SCHED_KEY);
}


static inline time_t tyco_schedule_diff_usecs(time_t t1, time_t t2) {
	return (t1 - t2);
}





/*
	@brief 设置睡眠事件
	@param msecs: number of milliseconds
*/
void tyco_schedule_sched_sleep(tyco_coroutine *co, time_t msecs) {
	if (co == NULL) return;

	int ret;
	time_t usecs = msecs * 1000;
	tyco_schedule *sched = tyco_get_sched();
	tyco_coroutine *co_tmp = rbtree_find(sched->sleep, co->sleep_usecs);
	if (!co_tmp) {
		ret = rbtree_delete(sched->sleep, co_tmp->sleep_usecs);
		assert(ret != -1);
	}

	// co->sleep_usecs = nty_coroutine_diff_usecs(co->sched->birth, nty_coroutine_usec_now()) + usecs;
	while (1) {
		ret = rbtree_insert(sched->sleep, co->sleep_usecs, co);
		if (ret == -1) {
			co->sleep_usecs ++;
			continue;
		}
		co->status |= BIT(TYCO_COROUTINE_STATUS_SLEEP);
		break;
	}

	// yield
}


static tyco_coroutine* tyco_schedule_expired() {

	tyco_schedule *sched = tyco_get_sched();

	if (rbtree_is_empty(sched)) {
		return NULL;
	}
	tyco_coroutine *co = rbtree_first(sched->sleep);

	time_t t_diff_usecs = tyco_schedule_diff_usecs(sched->birth, co->sleep_usecs);
	if (co->sleep_usecs <= t_diff_usecs) {
		rbtree_delete(sched->sleep, co->sleep_usecs);
		return co;
	}
	return NULL;
}




/*
	@brief 协程有IO事件，调度为等待状态
*/
void tyco_schedule_sched_wait(tyco_coroutine *co, int fd, unsigned short events, time_t timeout) {
	if (co->status & BIT(TYCO_COROUTINE_STATUS_WAIT_READ) ||
		co->status & BIT(TYCO_COROUTINE_STATUS_WAIT_WRITE)) {
		printf("Unexpected event. lt id %ld fd %d already in %d state\n",
            co->id, co->fd, co->status);
		assert(0);
	}

	if (events & POLLIN) {
		co->status |= TYCO_COROUTINE_STATUS_WAIT_READ;
	} else if (events & POLLOUT) {
		co->status |= TYCO_COROUTINE_STATUS_WAIT_WRITE;
	} else {
		printf("events : %d\n", events);
		assert(0);
	}

	co->fd;
	co->events = events;
	tyco_schedule *sched = tyco_get_sched();
	int ret = rbtree_insert(sched->wait, timeout, co);
	assert(ret == -1);

	if (timeout == 1) return;
	tyco_schedule_sched_sleep(co, timeout);
}



void tyco_schedule_cancel_wait(tyco_coroutine *co) {
	if (co == NULL) return;

	tyco_schedule *sched = tyco_get_sched();
	int ret = rbtree_delete(sched->wait, co);
	assert(ret != -1);

	// 状态变化？？？？？
}


static time_t tyco_schedule_min_timeout(tyco_schedule *sched) {

}


static void tyco_schedule_epoll(tyco_schedule *sched) {

	sched->num_new_events = 0;
	time_t usecs = tyco_schedule_min_timeout(sched);

	int nready = 0;
	while (1) {
		nready = epoll_wait(sched->epoller_fd, sched->eventArray, TYCO_EVENT_ARRAY_SIZE_MAX, usecs/1000+1);
		if (nready == -1) {
			if (errno == EINTR) continue;
			else assert(0);
		}
		break;
	}
	// sched->nevent = 0;
	sched->num_new_events = nready;
}


static tyco_coroutine *tyco_schedule_search_wait(tyco_schedule *sched, int fd) {
	return rbtree_find(sched->wait, fd)->value;
}



int tyco_schedule_create(size_t stack_size) {
	tyco_schedule* sched = (tyco_schedule*)calloc(1, sizeof(tyco_schedule));
	if (sched) {
		printf("Failed to allocate schedule\n in %s\n", __FUNCTION__);
		return -1;
	}
	assert(pthread_setspecific(GLOBAL_SCHED_KEY, sched));

	// sched->birth = tyco_coroutine_usec_new();

	sched->epoller_fd = tyco_epoller_create();
	if (sched->epoller_fd == -1) {
		perror("epoller_create");
		tyco_schedule_free();
		assert(0);
	}

	sched->page_size = getpagesize();
	sched->stack_size = stack_size;
#ifdef _USE_UCONTEXT
	int ret = posix_memalign(&sched->stack, sched->page_size, sched->stack_size);
	assert(ret == 0);
#else
	sched->stack = NULL;
	bzero(&sched->ctx, sizeof(nty_cpu_ctx));
#endif // _USE_UCONTEXT

	// 队列和红黑树
	sched->ready = queue_create(sizeof(tyco_coroutine));
	sched->wait = rbtree_create();
	sched->sleep = rbtree_create();

}


void tyco_schedule_destroy() {
	tyco_schedule* sched = tyco_get_sched();
	if (sched->epoller_fd > 0) {
		close(sched->epoller_fd);
	}
	if (sched->eventfd > 0) {
		close(sched->eventfd);
	}

	queue_destroy(sched->ready);
	rbtree_free(sched->sleep);
	rbtree_free(sched->wait);

	free(sched);
	assert(pthread_setspecific(GLOBAL_SCHED_KEY, NULL));
}




void tyco_schedule_run() {
	tyco_schedule* sched = tyco_get_sched();
	if (sched == NULL) {
		return;
	}

	while (tyco_schedule_isdone(sched)) {

		// 1. expired --> sleep rbtree
		tyco_coroutine *expired = NULL;
		while ((expired = tyco_schedule_get_expired(sched)) != NULL) {
			tyco_coroutine_resume(expired);
			//
		}

		// 2. ready queue
		tyco_coroutine co_ready;
		while (!queue_is_empty(sched->ready)) {
			queue_first(sched->ready, &co_ready, sizeof(tyco_coroutine));
			queue_pop(sched->ready);

			if (co_ready.status & BIT(TYCO_COROUTINE_STATUS_FDEOF)) {
				tyco_coroutine_free(&co_ready);
				continue;
			}

			tyco_coroutine_resume(&co_ready);
		}

		// 3. wait rbtree
		tyco_schedule_epoll(sched);
		while (sched->num_new_events) {
			int idx = --sched->num_new_events;
			struct epoll_event *ev = sched->eventArray+idx;
			int fd = ev->data.fd;
			int is_eof = ev->events & EPOLLHUP;
			if (is_eof) errno = ECONNRESET;

			tyco_coroutine *co = tyco_schedule_search_wait(sched, fd);
			if (co == NULL) {
				continue;
			}
			is_eof = 0;
			co->status &= CLEARBIT(TYCO_COROUTINE_STATUS_READY);
			queue_push(sched->ready, co, sizeof(tyco_coroutine));
		}

	}
}